package model.map;

import java.util.*;

import model.area_effect.Interactable;
import model.area_effect.MapAreaOfEffect;
import model.area_effect.SkillObject;
import model.area_effect.Trap;
import model.entity.Entity;
import model.item.InventoryItem;
import model.item.MapGold;

/**
 * This is the model's main view of the map.
 * @author Zachary
 *
 */
public class GameMap {
	private int xDimension;
	private int yDimension;
	
	// lots o' maps
	private Map<MapPoint, Terrain> terrainMap;
	private Map<MapPoint, MapTileState> stateMap;
	private Map<MapPoint, String> decalMap;
	private Map<MapPoint, List<SkillObject>> skillObjectMap;
	private Map<MapPoint, List<MapAreaOfEffect>> mapAoEMap;
	private Map<MapPoint, List<Trap>> trapMap;
	private Map<MapPoint, List<InventoryItem>> itemMap;
	private Map<MapPoint, List<Entity>> entityMap;
	private Map<MapPoint, List<Obstacle>> obstacleMap;
	private Map<MapPoint, List<MapGold>> goldMap;
	
	private static Shrouded shrouded;
	private static CurrentlyViewable currViewable;
	/**
	 * Constructor for the GameMap.
	 * Takes in two dimensions.
	 * @param xDimension
	 * @param yDimension
	 */
	public GameMap(int xDimension, int yDimension){
		this.xDimension = xDimension;
		this.yDimension = yDimension;
		
		this.terrainMap = new HashMap<MapPoint, Terrain>();
		this.stateMap = new HashMap<MapPoint, MapTileState>();
		this.decalMap = new HashMap<MapPoint, String>();
		this.skillObjectMap = new HashMap<MapPoint, List<SkillObject>>();
		this.mapAoEMap = new HashMap<MapPoint, List<MapAreaOfEffect>>();
		this.trapMap = new HashMap<MapPoint, List<Trap>>();
		this.itemMap = new HashMap<MapPoint, List<InventoryItem>>();
		this.entityMap = new HashMap<MapPoint, List<Entity>>();
		this.obstacleMap = new HashMap<MapPoint, List<Obstacle>>();
		this.goldMap = new HashMap<MapPoint, List<MapGold>>();
	}
	
	/**
	 * Build a base map which includes terrain, map tile states, decals
	 * and obstacles.  First method to call when building a game map.
	 * Returns the built game map.
	 * @param terrain
	 * @param decals
	 * @param obstacles
	 */
	public void buildBaseMap(Map<MapPoint, Terrain> terrain, 
			Map<MapPoint, String> decals, Map<MapPoint, List<Obstacle>> obstacles){
		Set<MapPoint> terrainSet = terrain.keySet();
		Set<MapPoint> decalSet = decals.keySet();
		Set<MapPoint> obstacleSet = obstacles.keySet();
		
		for(MapPoint p : terrainSet){
			terrainMap.put(p, terrain.get(p));
			//System.out.println("**" + terrain.get(p).getGraphicID());
		}
		
		for(MapPoint p : decalSet)
			decalMap.put(p, decals.get(p));
		
		for(MapPoint p : obstacleSet)
			obstacleMap.put(p, obstacles.get(p));
	}
	
	/**
	 * Add the initial items to the game map.
	 * @param items
	 */
	public void buildItemMap(Map<MapPoint, List<InventoryItem>> items){
		Set<MapPoint> itemsForMap = items.keySet();
		
		for(MapPoint p : itemsForMap)
			itemMap.put(p, items.get(p));
	}
	
	/**
	 * Build the map area of effects into the map.
	 * @param effects
	 * @param traps
	 */
	public void buildMapAreaOfEffectMap(Map<MapPoint, List<MapAreaOfEffect>> effects){
		Set<MapPoint> effect = effects.keySet();
		
		for(MapPoint p : effect)
			mapAoEMap.put(p, effects.get(p));
	}
	
	/**
	 * Build the traps into the mpa.
	 * @param traps
	 */
	public void buildTrapMap(Map<MapPoint, List<Trap>> traps){
		Set<MapPoint> trap = traps.keySet();
		
		for(MapPoint p : trap)
			trapMap.put(p, traps.get(p));
	}
	
	/**
	 * Build the entities onto the map.
	 * @param entities
	 */
	public void buildEntityMap(Map<MapPoint, List<Entity>> entities){
		Set<MapPoint> ents = entities.keySet();
		
		for(MapPoint p : ents)
			entityMap.put(p, entities.get(p));
	}
	
	/**
	 * Build the gold onto the map.
	 * @param entities
	 */
	public void buildGoldMap(Map<MapPoint, List<MapGold>> gold){
		Set<MapPoint> goldPoints = gold.keySet();
		
		for(MapPoint p : goldPoints)
			goldMap.put(p, gold.get(p));
	}
	
	/**
	 * Build the gold onto the map.
	 * @param entities
	 */
	public void buildStateMap(Map<MapPoint, MapTileState> states){
		Set<MapPoint> statePoints = states.keySet();
		
		for(MapPoint p : statePoints)
			stateMap.put(p, states.get(p));
	}
	
	/**
	 * Returns whether or not a point is valid.
	 * @param point
	 * @return
	 */
	public boolean validLocation(MapPoint point){
		int xCoord = point.getXCoordinate();
		int yCoord = point.getYCoordinate();
		return xCoord <= this.xDimension && yCoord <= this.yDimension &&
			xCoord >= 0 && yCoord >= 0;
	}

	/**
	 * Add a SkillObject to the map at a given point.
	 * @param mapObject
	 * @param point
	 */
	public void addSkillObjectToTile(SkillObject mapObject, MapPoint point) {
		List<SkillObject> list = this.skillObjectMap.get(point);
		if(list == null){
			list = new ArrayList<SkillObject>();
			skillObjectMap.put(point, list);
		}
		list.add(mapObject);
	}
	
	/**
	 * Add a MapAreaOfEffect to the map at a given point.
	 * @param mapObject
	 * @param point
	 */
	public void addMapAreaOfEffectToTile(MapAreaOfEffect mapObject, MapPoint point) {
		List<MapAreaOfEffect> list = this.mapAoEMap.get(point);
		if(list == null){
			list = new ArrayList<MapAreaOfEffect>();
			mapAoEMap.put(point, list);
		}
		list.add(mapObject);
	}
	
	/**
	 * Remove s MapAreaOfEffect from the map.
	 * @param aoe
	 * @param point
	 */
	public void removeMapAreaOfEffect(MapAreaOfEffect aoe, MapPoint point) {
		List<MapAreaOfEffect> list = this.mapAoEMap.get(point);
		if(list != null && !list.isEmpty())
			list.remove(aoe);
	}
	
	/**
	 * Remove a MapGold from the map.
	 * @param mapGold 
	 * @param point
	 */
	public void removeMapGoldFromTile(MapGold mapGold, MapPoint point) {
		List<MapGold> list = this.goldMap.get(point);
		if(list != null && !list.isEmpty())
			list.remove(mapGold);
	}

	/**
	 * Remove a SkillObject from a given point.
	 * @param object
	 * @param point
	 */
	public void removeSkillObject(SkillObject object, MapPoint point) {
		List<SkillObject> list = this.skillObjectMap.get(point);
		if(list != null && !list.isEmpty())
			list.remove(object);
	}

	/**
	 * Remove a Trap from a given point.
	 * @param trap
	 * @param point
	 */
	public void removeTrap(Trap trap, MapPoint point) {
		List<Trap> list = this.trapMap.get(point);
		if(list != null && !list.isEmpty())
			list.remove(trap);
	}
	
	/**
	 * Return all of the interactable stuffs on a tile
	 * @param entityLocation
	 * @return
	 */
	public List<Interactable> getInteractablesOnTile(MapPoint entityLocation) {
		List<Interactable> interactables = new ArrayList<Interactable>();
		interactables.addAll(mapAoEMap.get(entityLocation));
		interactables.addAll(trapMap.get(entityLocation));
		interactables.addAll(skillObjectMap.get(entityLocation));
		return null;
		
	}
	
	/**
	 * Get the items at the given map point.
	 */
	public List<InventoryItem> getInventoryItemsOnTile(MapPoint point){
		return itemMap.get(point);
	}
	
	/**
	 * Removes the specified item from the point.
	 * @param item
	 * @param point
	 * @return
	 */
	public InventoryItem removeItemFromTile(InventoryItem item, MapPoint point){
		List<InventoryItem> items = itemMap.get(point);
		if(items == null)
			return null;
		InventoryItem ret = null;
		boolean entFound = false;
		int index;
		for(index = 0;index<items.size();index++){
			ret = items.get(index);
			if(ret.equals(items)){
				entFound = true;
				break;
			}
		}
		return entFound ? items.remove(index) : null;
	}
	
	/**
	 * Adds the specified item to the point.
	 * @param item
	 * @param point
	 */
	public void addItemToTile(InventoryItem item, MapPoint point){
		List<InventoryItem> items = itemMap.get(point);
		if(items == null){
			items = new ArrayList<InventoryItem>();
			itemMap.put(point, items);
		}
		items.add(item);
	}
	
	/**
	 * Returns the entities at a given position.
	 * @param point
	 * @return
	 */
	public List<Entity> getEntitiesOnTile(MapPoint point){
		return entityMap.get(point);
	}
	
	/**
	 * Returns the obstacles at a given position.
	 * @param point
	 * @return
	 */
	public List<Obstacle> getObstaclesOnTile(MapPoint point){
		return obstacleMap.get(point);
	}
	
	/**
	 * Gets the map gold on the mayp.
	 * @param point
	 * @return
	 */
	public List<MapGold> getMapGoldOnTile(MapPoint point) {
		return goldMap.get(point);
	}
	
	/**
	 * Removes the given entity from the given position.  Returns
	 * the entity removed.
	 * @param entity
	 * @param point
	 * @return
	 */
	public Entity removeEntityFromTile(Entity entity, MapPoint point){
		List<Entity> entities = entityMap.get(point);
		if(entities == null)
			return null;
		Entity ret = null;
		boolean entFound = false;
		int index;
		for(index = 0;index<entities.size();index++){
			ret = entities.get(index);
			if(ret.equals(entity)){
				entFound = true;
				break;
			}
		}
		return entFound ? entities.remove(index) : null;
	}
	
	/**
	 * Adds an entity to the given position.
	 * @param entity
	 * @param point
	 */
	public void addEntityToTile(Entity entity, MapPoint point){
		List<Entity> entities = entityMap.get(point);
		if(entities == null){
			entities = new ArrayList<Entity>();
			entityMap.put(point, entities);
		}
		entities.add(entity);
	}

	/**
	 * Get a terrain tile at a specific point.
	 * @param point
	 * @return
	 */
	public Terrain getTerrain(MapPoint point){
		return terrainMap.get(point);
	}
	
	/**
	 * Gets a decal at a specific point.
	 * @param point
	 * @return
	 */
	public String getDecal(MapPoint point){
		return decalMap.get(point);
	}
	
	/**
	 * Get the MapAoE's from a point.
	 * @param point
	 * @return
	 */
	public List<MapAreaOfEffect> getMapAreaOfEffect(MapPoint point){
		return mapAoEMap.get(point);
	}
	
	/**
	 * Get the Traps from a point.
	 * @param point
	 * @return
	 */
	public List<Trap> getTraps(MapPoint point){
		return trapMap.get(point);
	}
	
	/**
	 * Get the SkillObjects from a point.
	 * @param point
	 * @return
	 */
	public List<SkillObject> getSkillObjects(MapPoint point){
		return skillObjectMap.get(point);
	}
	
	/**
	 * Get the state at a specific point.
	 * @param point
	 * @return
	 */
	public MapTileState getTileState(MapPoint point){
		return stateMap.get(point);
	}
	
	/**
	 * Put a state at a specific point.
	 * @param point
	 * @param state
	 */
	public void setTileState(MapPoint point, MapTileState state){
		stateMap.put(point, state);
	}
	
	/**
	 * Returns the boundary of the map.
	 * @return
	 */
	public MapPoint getBoundary(){
		return new MapPoint(this.xDimension, this.yDimension);
	}
	
	/**
	 * Accepts a map visitor.
	 * @param v
	 */
	public void acceptMapVisitor(MapVisitor v){
		v.visit(this);
	}
	
	/**
	 * Applys the flash function for draw visitor.
	 */
	public void flash(DrawMapVisitor v){
		v.flash(this);
	}
}
